'use strict';

const { Controller } = require('egg');
const fs = require('fs');
const path = require('path');
const sendToWormhole = require('stream-wormhole');
const pump = require('mz-modules/pump');

class FilesController extends Controller {
  insufficientScope(ctx, reason) {
    ctx.set('WWW-Authenticate', 'Bearer realm=\"AUGENBLICK\",' +
      'error=\"insufficient_scope\",' +
      'error_description=\"' + reason + '\"');
    ctx.status = 403;
  }

  async new() {
    await this.ctx.render('form.html');
  }

  async index() {
    const { ctx } = this;
    if (!await ctx.service.files.checkScope()) {
      this.insufficientScope(ctx, 'Scope must contain \'file\'');
      return;
    }

    const user = await ctx.service.authorization.getCurrentUser();
    const data = await ctx.model.File.findAll({
      where: {
        user,
        folder: null,
      },
    });
    if (!data) ctx.body = [];
    else ctx.body = data;
  }

  async show() {
    const { ctx } = this;
    if (!await ctx.service.files.checkScope()) {
      this.insufficientScope(ctx, 'Scope must contain \'file\'');
      return;
    }

    const user = await ctx.service.authorization.getCurrentUser();
    const { id } = ctx.params;
    const file = await ctx.model.File.findByPk(id);
    if (!file) {
      ctx.throw(404, 'not_found');
      return;
    }
    if (file.user !== user) {
      ctx.throw(403, 'forbidden');
      return;
    }
    if (file.folder_type) {
      const data = await ctx.model.File.findAll({
        where: {
          folder: id,
        },
      });
      if (!data) file.dataValues.data = [];
      else file.dataValues.data = data;
      ctx.body = file;
      return;
    }

    const filePath = path.join(this.config.baseDir, 'files', id);
    if (!fs.existsSync(filePath)) throw new Error();
    ctx.set('Content-Type', file.mime);
    ctx.body = fs.createReadStream(filePath);
  }

  async createFolder() {
    const { ctx } = this;
    if (!await ctx.service.files.checkScope()) {
      this.insufficientScope(ctx, 'Scope must contain \'file\'');
      return;
    }
    ctx.validate({
      name: {
        type: 'string',
        max: 254,
      },
      folder: 'int?',
    });
    const { name, folder } = ctx.request.body;
    const user = await ctx.service.authorization.getCurrentUser();
    const folder_type = true;
    if (typeof folder !== 'undefined' && folder !== null) {
      await ctx.service.files.checkFolder(ctx, folder);
    }
    const folderModel = await ctx.model.File.create({
      user,
      name,
      folder_type,
      folder,
    });
    ctx.status = 201;
    ctx.body = {
      id: folderModel.id,
    };
  }

  async create() {
    const { ctx } = this;
    if (!await ctx.service.files.checkScope()) {
      this.insufficientScope(ctx, 'Scope must contain \'file\'');
      return;
    }
    const stream = await ctx.getFileStream();
    try {
      ctx.validate({
        folder: 'string?',
      }, stream.fields);

      let { folder } = stream.fields;
      if (folder === '') folder = null;
      else if (folder !== null && typeof folder !== 'undefined') {
        folder = Number(folder);
        if (isNaN(folder)) {
          ctx.throw(422, 'invalid_request');
          throw { status: 422 };
        }
      }
      const mime = stream.mime;
      const user = await ctx.service.authorization.getCurrentUser();
      const folder_type = false;

      if (typeof folder !== 'undefined' && folder !== null) {
        await ctx.service.files.checkFolder(ctx, folder);
      }
      const result = await ctx.model.transaction(async t => {
        const file = await ctx.model.File.create({
          user,
          name: path.basename(stream.filename),
          mime,
          folder_type,
          folder,
        }, { transaction: t });
        const target = path.join(this.config.baseDir, 'files', file.id.toString());
        if (fs.existsSync(target)) {
          fs.unlinkSync(target);
        }
        const writeStream = fs.createWriteStream(target);
        await pump(stream, writeStream);
        return file;
      });
      ctx.status = 201;
      ctx.body = {
        id: result.id,
      };
    } catch (e) {
      await sendToWormhole(stream);
      throw e;
    }
  }

  async update() {
    const { ctx } = this;
    if (!await ctx.service.files.checkScope()) {
      this.insufficientScope(ctx, 'Scope must contain \'file\'');
      return;
    }
    const { id } = ctx.params;
    ctx.validate({
      name: {
        type: 'string',
        max: '254',
      },
    });
    const { name } = ctx.request.body;
    const file = await ctx.model.File.findByPk(id);
    if (!file) {
      ctx.throw(404, 'not_found');
      return;
    }
    const user = await ctx.service.authorization.getCurrentUser();
    if (file.user !== user) {
      ctx.throw(403, 'forbidden');
      return;
    }
    await file.update({ name });
    ctx.status = 204;
  }

  async destroy() {
    const { ctx } = this;
    if (!await ctx.service.files.checkScope()) {
      this.insufficientScope(ctx, 'Scope must contain \'file\'');
      return;
    }
    const { id } = ctx.params;
    const file = await ctx.model.File.findByPk(id);
    if (!file) {
      ctx.throw(404, 'not_found');
      return;
    }
    const user = await ctx.service.authorization.getCurrentUser();
    if (file.user !== user) {
      ctx.throw(403, 'forbidden');
      return;
    }
    await file.destroy();
    ctx.status = 204;
  }
}

module.exports = FilesController;
